package com.lagou.mvcframework.servlet;


import com.lagou.mvcframework.annotations.*;
import com.lagou.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LagouDispatcherServlet extends HttpServlet {

    //存放lagoumvc.properties的参数
    private Properties properties = new Properties();

    //存放扫描到的类的全限定名
    private List<String> classNames = new ArrayList<>();

    //单例池
    private Map<String, Object> singletonObjects = new HashMap<>();

    //处理器映射器：HandlerMapping
    private List<Handler> handlerMapping = new ArrayList<>();


    @Override
    public void init(ServletConfig config) throws ServletException {

        //读取lagoumvc配置文件
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");

        //1.加载配置文件：lagoumvc.properties
        doLoadConfig(contextConfigLocation);

        //2.扫描相关的类
        doScan(properties.getProperty("scanPackage"));

        //3.实例化bean
        doInstance(classNames);

        //4.维护bean的依赖关系，实现依赖注入
        doAutowired();

        //5.构造一个处理器映射器，维护url和method的映射关系
        initHandlerMapping();

        System.out.println("lagou mvc framework 初始化完成！");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //根据uri获取handler
        Handler handler = getHandler(request);
        if (handler == null) {
            response.getWriter().write("404 Not Found");
            return;
        }

        //从请求中获取所有参数
        Map<String, String[]> parameterMap = request.getParameterMap();

        //权限控制
        if (handler.getUsePermission()) {
            List<Pattern> userNamePatternList = handler.getUserNamePatternList();
            if (userNamePatternList.isEmpty()) {//启用权限控制，但userNamePatternList为空，视为禁止一切访问
                response.getWriter().write("No Permission");
                return;
            }
            String[] username = null;
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                if (!"username".equals(entry.getKey())) {
                    continue;
                }
                username = entry.getValue();
            }
            if (username == null) {//未传username视为无权限
                response.getWriter().write("No Permission");
                return;
            }
            boolean hasPermission = false;//标记是否有权限
            for (String s : username) {
                for (Pattern pattern : userNamePatternList) {
                    if (pattern.matcher(s).matches()) {
                        hasPermission = true;
                    }
                }
            }
            if (!hasPermission) {//无权限
                response.getWriter().write("No Permission");
                return;
            }
        }

        Object controller = handler.getController();
        Method method = handler.getMethod();
        Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();

        try {
            //没有参数
            if (paramIndexMapping.isEmpty()) {
                method.invoke(controller, null);
            } else {
                //组织可变参
                Object[] params = new Object[handler.getMethod().getParameterTypes().length];

                //遍历request中所有参数
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

                    String key = entry.getKey();//参数key
                    String[] valueArray = entry.getValue();//参数value

                    //此种形式的传参是被允许的：name=zhangsan&name=lisi，后台应解析为：name=zhangsan,lisi
                    String value = StringUtils.join(valueArray, ",");

                    if (!paramIndexMapping.containsKey(key)) {
                        continue;
                    }
                    Integer index = paramIndexMapping.get(key);
                    params[index] = value;//把前台传递过来的参数填充到对应位置去
                }

                //填充一些servlet相关参数
                if (handler.getParamIndexMapping().containsKey(HttpServletRequest.class.getSimpleName())) {
                    Integer index = paramIndexMapping.get(HttpServletRequest.class.getSimpleName());
                    params[index] = request;
                }
                if (handler.getParamIndexMapping().containsKey(HttpServletResponse.class.getSimpleName())) {
                    Integer index = paramIndexMapping.get(HttpServletResponse.class.getSimpleName());
                    params[index] = response;
                }

                method.invoke(controller, params);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }


    /**
     * 加载配置文件：使用Properties类
     *
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 扫描指定包下的类
     *
     * @param scanPackage com.lagou.demo
     */
    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {//子包
                //递归调用
                doScan(scanPackage + "." + file.getName());//com.lagou.demo.controller
            } else if (file.getName().endsWith("class")) {
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }

    /**
     * 实例化bean，放入单例池
     */
    private void doInstance(List<String> classNames) {
        if (classNames.size() == 0) {
            return;
        }
        for (String className : classNames) {
            try {
                Class<?> aClass = Class.forName(className);
                if (aClass.isAnnotationPresent(LagouController.class)) {
                    String simpleName = aClass.getSimpleName();
                    String lowerFirstSimpleName = lowerFirstCharacter(simpleName);
                    Object o = aClass.newInstance();
                    singletonObjects.put(lowerFirstSimpleName, o);
                } else if (aClass.isAnnotationPresent(LagouService.class)) {
                    LagouService lagouService = aClass.getAnnotation(LagouService.class);
                    String value = lagouService.value();
                    Object o = aClass.newInstance();
                    //指定了name就以指定的为准
                    if (!"".equalsIgnoreCase(value)) {
                        singletonObjects.put(value, o);
                    } else {//没指定name，就以类名首字母小写作为name
                        String simpleName = aClass.getSimpleName();
                        String lowerFirstSimpleName = lowerFirstCharacter(simpleName);
                        singletonObjects.put(lowerFirstSimpleName, o);
                    }
                    //service层往往是有接口的，面向接口开发，此时再以接口名为name，放入一份对象到ioc容器中，便于后期根据接口类型注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        singletonObjects.put(anInterface.getName(), o);
                    }
                } else {
                    continue;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 维护bean依赖关系，实现依赖注入
     */
    private void doAutowired() {
        if (singletonObjects.isEmpty()) {
            return;
        }
        try {
            for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {
                //获取所有Field，不能用getFields
                Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
                if (declaredFields.length == 0) {
                    continue;
                }
                for (Field declaredField : declaredFields) {
                    if (!declaredField.isAnnotationPresent(LagouAutowired.class)) {
                        continue;
                    }
                    Object bean = null;
                    LagouAutowired annotation = declaredField.getAnnotation(LagouAutowired.class);
                    String value = annotation.value();
                    if (!"".equalsIgnoreCase(value)) {
                        bean = singletonObjects.get(value);
                    }
                    if (bean == null) {
                        singletonObjects.get(declaredField.getType().getName());
                    }
                    if (bean == null) {
                        bean = singletonObjects.get(declaredField.getName());
                    }
                    if (bean == null) {
                        //找不到，抛出ClassNotFoundException
                        throw new ClassNotFoundException();
                    }
                    //开启强制赋值
                    declaredField.setAccessible(true);
                    declaredField.set(entry.getValue(), bean);
                }
            }
        } catch (ClassNotFoundException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 最关键的环节，构建一个处理器映射器
     * <p>
     * 目的：建立url和handler之间的映射，使用对象维护关联关系
     */
    private void initHandlerMapping() {
        if (singletonObjects.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {

            String key = entry.getKey();//bean的name
            Object value = entry.getValue();//bean对象

            Class<?> aClass = value.getClass();
            if (!aClass.isAnnotationPresent(LagouController.class)) {
                continue;
            }
            String beseUrl = "";
            if (aClass.isAnnotationPresent(LagouRequestMapping.class)) {
                LagouRequestMapping lagouRequestMapping = aClass.getAnnotation(LagouRequestMapping.class);
                beseUrl = lagouRequestMapping.value();//等同于/demo
            }
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(LagouRequestMapping.class)) {
                    continue;
                }
                LagouRequestMapping lagouRequestMapping = method.getAnnotation(LagouRequestMapping.class);
                String methodUrl = lagouRequestMapping.value();//等同于/query


                String url = beseUrl + methodUrl;//等同于/demo/query

                //一个method装配一个handler
                Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url));


                //装配paramIndexMapping
                Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();
                Parameter[] parameters = method.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];

                    //servlet相关参数
                    if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                        paramIndexMapping.put(parameter.getType().getSimpleName(), i);
                    } else {//普通参数z
                        paramIndexMapping.put(parameter.getName(), i);//<name,2>
                    }

                }

                //装配userNamePatternList
                LagouSecurity lagouSecurity = method.getAnnotation(LagouSecurity.class);
                if (lagouSecurity == null) {//方法上有@LagouSecurity，优先使用，否则去类上寻找，就近原则
                    lagouSecurity = aClass.getAnnotation(LagouSecurity.class);
                }
                if (lagouSecurity != null) {//启用权限控制
                    String[] userNames = lagouSecurity.value();
                    List<Pattern> userNamePatternList = handler.getUserNamePatternList();
                    for (String userName : userNames) {
                        userNamePatternList.add(Pattern.compile(userName));
                    }
                } else {//不启用权限控制
                    handler.setUsePermission(false);
                }

                handlerMapping.add(handler);
            }
        }
    }

    /**
     * 首字母小写工具方法
     */
    private String lowerFirstCharacter(String str) {
        char[] chars = str.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    /**
     * 根据uri查找handler
     */
    private Handler getHandler(HttpServletRequest request) {
        if (handlerMapping.isEmpty()) {
            return null;
        }

        //获取请求的uri：/demo/query
        String requestURI = request.getRequestURI();


        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(requestURI);
            if (!matcher.matches()) {
                continue;
            }
            return handler;
        }
        return null;
    }
}
